
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>我敏感的心</title>

<style>
@import url('https://fonts.googleapis.com/css?family=Calligraffitti');
body{
	background-color: #000;
	font-family: Arial, Helvetica, sans-serif;
}
#container{
	position: relative;
}
#wrapper{
	width: 100%;
	position: absolute;
	text-align: center;
}
#words{
	pointer-events:none;
	font-family: 'Calligraffitti', cursive;
	position: absolute;
	width: 100%;
	padding: 200px 0 0 0;
	font-size: 2.8em;
	color: white;
	text-align: center;
}
#words p{
	display: none;
}
#words p#w1{
	display: block;
}
#words p span{
	display: block;
	font-size: .4em;
}
#UI{
	color: white;
	width: 200px;
	cursor: pointer;
	position: relative;
	display: none;
}
#behaviours{
	margin-top: 10px;
}
#behaviours label span{
	display: inline-block;
	font-size: .7em;
}
#behaviours input{
	width: 50px;
	margin-bottom: 10px;
	margin-top: -20px;
}</style>
</head>
<body>
<div id=container>
	<div id="wrapper"></div>
	<div id="words">
		<p id="w1">My Sensitive Heart</p>
		<p id="w2">you are my guiding star</p>
		<p id="w3">my heart</p>
		<p id="w4">beats for you</p>
		<p id="w5">you send me</p>
		<p id="w6">into the sun</p>
		<p id="w7">into chaos</p>
		<p id="w8">i am all a'quiver!</p>
		<p id="w9">Play with me!<span>move your mouse</span></p>
	</div>
	<div id="UI">
		<div><strong>SHAPES:</strong></div>
			<ul>
				<li id="heart">heart</li>
				<li id="star">star</li>
				<li id="circle">circle</li>
				<li id="hallow">hallows</li>
				<li id="kiss">kiss</li>
			</ul>

		<div id="behaviours">
			<div><strong>BEHAVIOURS:</strong></div>
			<label for="wander">wander <span>(how far do you stray?)</span></label><br />
				<input id="wander" value="0.05" min="0.05" step="0.05" type="number"/><br />
			<label for="friction">friction <span>(f>1: faster, faster! f<1: slow....)</span></label><br />
				<input id="friction" value=".9" min="0.5" step="0.05" type="number"/><br />
			<label for="mouseDistance">mouse distance <span>(how far do you feel it?)</span>:</label><br />
				<input id="mouseDistance" value="100" min="0" step="5" type="number"/><br />
			<label for="mouseForce">mouse force <span>(how powerful is the effect!)</span></label><br />
				<input id="mouseForce" value="0.05" min="0" step="0.05" type="number"/><br />
			<label for="springForce">particle spring force: <span>(springy or bouncy (strict or flexible))</span></label><br />
				<input id="springForce" value="0.01" min="0" step="0.01" type="number"/><br />
		</div>
	</div>
</div>

<script>
var RUN_AS_ANIMATION=true,

wrapper, canvas, context, width=600, height=600, Tau=Math.PI*2,
systems=[],
mouseX=0, mouseY=0, repelMouse = true,
copyCount=1, sequenceCount=0, maxSequenceCount=22, sequenceDelay=2000,
orbitting=false, orbitAngle, orbitDegrees=0, orbitSpeed=1, orbitalRadius=10, PI180=Math.PI/180, orbitVX, orbitVY,
colourInterval,
repaintColour="rgba(0,0,0, .05)",
pathObjects=[];
pathObjects.push( {pLength:0, dom:null, pcStep:0, pt:null, id:"heart", shape:"M 209.95 88.4 Q 210 86.85 210 86.5 210 86.05 210 85.6 210.65 27.5 177.7 7.25 143.7 -13.75 104.9 26 66.3 -13.75 32.3 7.25 -1.85 28.2 0.1 89.75 2.05 151.35 104.9 230 207.95 151.35 209.9 89.75 209.9 89.05 209.95 88.4 Z"} ),
pathObjects.push( {pLength:0, dom:null, pcStep:0, pt:null, id:"star", shape:"M 0 115.05 L 81.35 86.25 83.55 0 136 68.5 218.7 43.95 169.8 115.05 218.7 186.1 136 161.55 83.55 230 81.35 143.8 0 115.05 Z"} ),
pathObjects.push( {pLength:0, dom:null, pcStep:0, pt:null, id:"circle", shape:"M 220 110 Q 220 155.6 187.75 187.8 155.6 220 110 220 64.4 220 32.15 187.8 0 155.6 0 110 0 64.4 32.15 32.2 64.4 0 110 0 155.6 0 187.75 32.2 220 64.4 220 110 Z"} ),
pathObjects.push( {pLength:0, dom:null, pcStep:0, pt:null, id:"smallHeart", shape:"M 174.15 97.5 Q 174.2 96.45 174.2 96.25 174.2 95.95 174.2 95.65 174.6 57.35 152.9 44.05 130.5 30.2 104.95 56.4 79.55 30.2 57.15 44.05 34.65 57.85 35.9 98.35 37.2 138.95 104.95 190.75 172.85 138.95 174.15 98.35 174.15 97.9 174.15 97.5 Z"} ),
pathObjects.push( {pLength:0, dom:null, pcStep:0, pt:null, id:"hallow", shape:"M 220 188.7 L 219.5 187.75 Q 219.25 188.25 219.05 188.7 L 220 188.7 Z M 219.5 187.75 L 107.3 0.95 107.3 63.075 Q 107.85 63.05 108.4 63.05 116.65 63.05 124.15 64.95 140.05 68.9 152.5 81.3 170.75 99.6 170.75 125.4 170.75 139.9 165 152.05 M 219.05 188.7 L 106.95 188.7 0 188.7 107 0.45 107.3 0.95 M 106.95 188.7 L 106.95 187.75 106.9 187.75 Q 82 187.25 64.3 169.5 46 151.25 46 125.4 46 111.2 51.55 99.3 56.1 89.5 64.3 81.3 82 63.55 106.9 63.1 L 106.95 63.1 Q 107.125 63.087890625 107.3 63.075 L 107.3 187.75 Q 107.480859375 187.75 107.65 187.75 108 187.75 108.4 187.75 134.2 187.75 152.5 169.5 160.3 161.7 164.75 152.5 M 107.3 187.75 Q 107.1302734375 187.75 106.95 187.75"} ),
pathObjects.push( {pLength:0, dom:null, pcStep:0, pt:null, id:"kiss", shape:"M 220 111.85 Q 219.6 112.1 219.2 112.35 187.4 131.35 162.3 153.55 141.15 172.15 110.5 173.35 79.15 172.4 57.8 153.55 32.6 131.3 0.6 112.2 0.3 112.05 0.05 111.85 M 219.2 112.35 Q 215.5 113.85 202.2 106.5 187.95 98.5 165 71 142.4 43.9 109.9 62.05 77.35 43.9 54.75 71 31.8 98.5 17.55 106.5 4.25 113.85 0.6 112.2 M 36.3 113.85 Q 65.95 119.15 84.55 120.75 109.25 111.8 130.8 120.05 151.35 118.9 179.7 113.85 M 84.55 120.75 Q 109.25 127.95 130.8 120.05"} ),


window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          window.oRequestAnimationFrame      ||
          window.msRequestAnimationFrame     ||
          function(callback, element){
            window.setTimeout(callback, 1000 / 60);
          };
})();

var ParticleSystem = function(id){
    Object.defineProperty(this, 'colour', { value:'#FF1515', writable:true });
    Object.defineProperty(this, 'friction', { value:.9, writable:true });                // f < 1 == slows;  f > 1 == speeds up
    Object.defineProperty(this, 'height', { value:100, writable:true });
    Object.defineProperty(this, 'id', { value:id, writable:true} );
    Object.defineProperty(this, 'maxSpeed', { value:10, writable:true });
    Object.defineProperty(this, 'mouseRepelDist', { value:100, writable:true });         // Size of repel circle
    Object.defineProperty(this, 'mouseForce', { value:.05, writable:true });             // dist particle moves when influenced by mouse. lower number: blends shape more: .05 - n
    Object.defineProperty(this, 'numParticles', { value:50, writable:true }); 
    Object.defineProperty(this, 'particles', { value:[], writable:true });
    Object.defineProperty(this, 'repelMouse', { value:true, writable:true });
    Object.defineProperty(this, 'springTo', { value:true, writable:true });
    Object.defineProperty(this, 'springForce', { value:.01, writable:true });           // speed of return to spring point. tightness
    Object.defineProperty(this, 'wandering', { value:true, writable:true });
    Object.defineProperty(this, 'wander', { value:.05, writable:true });                  // bigger number - fuzzier .05 just perceptible
    Object.defineProperty(this, 'wanderMod', { value:this.wander*.5, writable:true });
    Object.defineProperty(this, 'width', { value:100, writable:true });
    Object.defineProperty(this, 'x', { value:0, writable:true });
    Object.defineProperty(this, 'y', { value:0, writable:true });
}
ParticleSystem.prototype.setWander = function(value){ this.wander = value; this.wanderMod = value*.5 }

ParticleSystem.prototype.generate = function(){
    for(var j=0; j<pathObjects.length; j+=1){
        pathObjects[j].pcStep = pathObjects[j].pLength / this.numParticles;
    }
    var pShapeArrayX, pShapeArrayY;
    for(var i=0; i<this.numParticles; i++){
        // pass in pattern points
        pShapeArrayX = [];
        pShapeArrayY = [];
        for(var p=0; p<pathObjects.length; p+=1){
            pathObjects[p].pt = pathObjects[p].dom.getPointAtLength(i*pathObjects[p].pcStep);
            pShapeArrayX.push(pathObjects[p].pt.x + ((this.width-220)/2));
            pShapeArrayY.push(pathObjects[p].pt.y + ((this.height-230)/2));
        }
        var p = new Particle("p"+i, pShapeArrayX, pShapeArrayY, this);
        this.particles.push(p);
    }
}

ParticleSystem.prototype.update = function(){
    var particle;
    for(var i=0; i<this.numParticles; i++){
        particle = this.particles[i];
        particle.pUpdate();
    }
}

ParticleSystem.prototype.pShapeMorph = function(value){
    var particle;
    for(var i=0; i<this.numParticles; i++){
        particle = this.particles[i];
        particle.pChangeSpringPoint(value);
    }
}

var Particle = function(id, x, y, parentSystem){
    Object.defineProperty(this, 'id', { value:id, writable:true });
    Object.defineProperty(this, 'shapesX', { value:x, writable:true });
    Object.defineProperty(this, 'shapesY', { value:y, writable:true });
    Object.defineProperty(this, 'x', { value:this.shapesX[0], writable:true });
    Object.defineProperty(this, 'y', { value:this.shapesY[0], writable:true });
    Object.defineProperty(this, 'sx', { value:this.x, writable:true });
    Object.defineProperty(this, 'sy', { value:this.y, writable:true });
    Object.defineProperty(this, 'vx', { value:0, writable:true });
    Object.defineProperty(this, 'vy', { value:0, writable:true });
    Object.defineProperty(this, 'densityScore', { value:1, writable:true });
    Object.defineProperty(this, 'parentSystem', { value:parentSystem, writable:true });
    return this;
}

Particle.prototype.pChangeSpringPoint = function(value){
    // change spring point - causes morph
    if(value < 0) value = 0;
    if(value >= this.x.length) value = this.x.length-1;
    this.sx = this.shapesX[value];
    this.sy = this.shapesY[value];
}

Particle.prototype.pUpdate = function(){
    
    // randomise movement slightly
    if(this.parentSystem.wandering){
        this.vx += Math.random() * this.parentSystem.wander - this.parentSystem.wanderMod;
        this.vy += Math.random() * this.parentSystem.wander - this.parentSystem.wanderMod;
    }

    // apply friction
    this.vx *= this.parentSystem.friction;
    this.vy *= this.parentSystem.friction;

    // constrain to speed bounds
    var speed = Math.sqrt(this.vx*this.vx + this.vy*this.vy);
    if (speed > this.parentSystem.maxSpeed) {
        this.vx = this.parentSystem.maxSpeed * this.vx / speed;
        this.vy = this.parentSystem.maxSpeed * this.vy / speed;
    }

    // stay attached to point
    if(this.parentSystem.springTo){
        this.vx += (this.sx - this.x) * this.parentSystem.springForce;
        this.vy += (this.sy - this.y) * this.parentSystem.springForce;
    }

    // avoid the mouse
    if(this.parentSystem.repelMouse){
        var dx = mouseX - this.x;
        var dy = mouseY - this.y;
        var dist = Math.sqrt(dx*dx + dy*dy);
        if (dist < this.parentSystem.mouseRepelDist) {
            var tx = mouseX - this.parentSystem.mouseRepelDist * dx / dist;
            var ty = mouseY - this.parentSystem.mouseRepelDist * dy / dist;
            this.vx += (tx - this.x) * this.parentSystem.mouseForce;
            this.vy += (ty - this.y) * this.parentSystem.mouseForce;
        }
    }

    // update position
    this.x += this.vx;
    this.y += this.vy;
}

function get(id) {
    return document.getElementById(id);
}

function createCanvas(id, w, h){
    var tCanvas = document.createElement("canvas");
    tCanvas.width = w;
    tCanvas.height = h;
    tCanvas.id = id;
    return tCanvas;
}

function init(){
    var tPath;
    for(var i=0; i<pathObjects.length; i+=1){
        tPath = document.createElementNS('http://www.w3.org/2000/svg','path');
        tPath.setAttribute('d', pathObjects[i].shape);
        pathObjects[i].dom = tPath;
        pathObjects[i].pLength = tPath.getTotalLength();
    }

    wrapper = get("wrapper");
    canvas =  createCanvas("canvas", width, height);
    wrapper.appendChild(canvas);
    context = canvas.getContext("2d");

    /* Define new ParticleSystem and set values */
    var system1 = new ParticleSystem('pSys1');
    systems.push(system1);
    system1.width = width;
    system1.height = height;
    system1.maxSpeed = 3;
    system1.scalar = .3;
    system1.numParticles = 100;
    system1.x = 150;
    system1.y = 150;
    system1.setWander(1);
    system1.generate();
}

function update(){
    for(var i=0; i<systems.length; i++){
        systems[i].update();
    }
    if(orbitting){
        doOrbit();
    }
}

function draw(){
    var system;
    for(var i=0; i<systems.length; i++){
        system = systems[i];
        context.fillStyle = system.colour;
        var particle;
        for(var j=0; j<system.numParticles; j+=1) {
            particle = system.particles[j];
            context.beginPath();
            if(particle.densityScore > 0){
                context.arc(particle.x, particle.y, particle.densityScore, 0, Tau, false);
            }
            context.fill();
        }
    }
}

function animate(){
    context.fillStyle = repaintColour;
    context.fillRect(0, 0, width, height);
    update();
    draw();
    requestAnimFrame(animate);
}

function setUpEvents(){
    function getMousePos(canvas, evt) {
        var rect = canvas.getBoundingClientRect();
        return {
            x: evt.clientX - rect.left,
            y: evt.clientY - rect.top
        };
    }
    canvas.addEventListener('mousemove', function(evt) {
        var mousePos = getMousePos(canvas, evt);
        mouseX = mousePos.x;
        mouseY = mousePos.y;
    }, false);
}

function doOrbit(){
    orbitAngle = orbitDegrees * PI180;
    orbitDegrees += orbitSpeed;
    orbitVX = orbitalRadius * Math.cos(orbitAngle);
    orbitVY = orbitalRadius * Math.sin(orbitAngle);
    mouseX = orbitVX + width*.5;
    mouseY = orbitVY + height*.5;
    // expand orbit
    orbitSpeed*=1.003;
    orbitalRadius*=1.003;
}

function randomColour(){
    var r = Math.round(Math.random()*125)+130,
    g = Math.round(Math.random()*155)+100,
    b = Math.round(Math.random()*155)+100;
    return 'rgb('+r+','+g+','+b+')';
}

function clickHandler(evt){
    switch(evt.target.id){
        case "heart":
            systems[0].pShapeMorph(0);
            break;
        case "star":
            systems[0].pShapeMorph(1);
            break;
        case "circle":
            systems[0].pShapeMorph(2);
            break;
        case "hallow":
            systems[0].pShapeMorph(4);
            break;
        case "kiss":
            systems[0].pShapeMorph(5);
            break;
        case "wander":
            systems[0].setWander(evt.target.value);
            break;
        case "friction":
            systems[0].friction = evt.target.value;
            break;
        case "mouseDistance":
            systems[0].mouseRepelDist = evt.target.value;
            break;
        case "mouseForce":
            systems[0].mouseForce = evt.target.value;
            break;
        case "springForce":
            systems[0].springForce = evt.target.value;
            break;
        default:
            /* [[-_-]] */
    }
}

function enableUI(){
    get("heart").addEventListener("click", clickHandler, false);
    get("star").addEventListener("click", clickHandler, false);
    get("circle").addEventListener("click", clickHandler, false);
    get("hallow").addEventListener("click", clickHandler, false);
    get("kiss").addEventListener("click", clickHandler, false);

    get('wander').addEventListener("change", clickHandler, false);
    get('friction').addEventListener("change", clickHandler, false);
    get('mouseDistance').addEventListener("change", clickHandler, false);
    get('mouseForce').addEventListener("change", clickHandler, false);
    get('springForce').addEventListener("change", clickHandler, false);
    get('UI').style.display = "block";
}

function sequencer(){
    sequenceCount++;
    switch(sequenceCount){
        case 2:
            sequenceDelay=3000;
            systems[0].colour = '#CACAF8';
            get("w"+(copyCount++)).style.display="none";
            get("w"+copyCount).style.display="block";
            systems[0].pShapeMorph(1);
            break;
        case 3:
            sequenceDelay=3000;
            systems[0].colour = '#FF1515';
            get("w"+(copyCount++)).style.display="none";
            get("w"+copyCount).style.display="block";
            systems[0].pShapeMorph(0);
            break;
        case 4:
            sequenceDelay=500;
            systems[0].colour = '#69359c';
            systems[0].pShapeMorph(3);
            break;
        case 5:
            sequenceDelay=500;
            systems[0].colour = '#FF1515';
            systems[0].pShapeMorph(0);
            break;
        case 6:
            sequenceDelay=500;
            systems[0].colour = '#69359c';
            get("w"+(copyCount++)).style.display="none";
            get("w"+copyCount).style.display="block";
            systems[0].pShapeMorph(3);
            break;
        case 7:
            sequenceDelay=600;
            systems[0].colour = '#FF1515';
            systems[0].pShapeMorph(0);
            break;
        case 8:
            sequenceDelay=500;
            systems[0].colour = '#69359c';
            systems[0].pShapeMorph(3);
            break;
        case 9:
            sequenceDelay=1000;
            systems[0].colour = '#FF1515';
            systems[0].pShapeMorph(0);
            break;
        case 10:
            sequenceDelay=3500;
            systems[0].pShapeMorph(2);
            get("w"+(copyCount++)).style.display="none";
            get("w"+copyCount).style.display="block";
            break;
        case 11:
            sequenceDelay=1200;
            get("w"+(copyCount++)).style.display="none";
            get("w"+copyCount).style.display="block";
            mouseX = width * .5;
            mouseY = height * .5;
            systems[0].mouseForce = 7;
            systems[0].mouseRepelDist = 175;
            systems[0].springForce = .1;
            systems[0].setWander(.05);
            break;
        case 12:
            sequenceDelay=9000;
            orbitting=true;
            break;
        case 13:
            sequenceDelay=10000;
            systems[0].colour = randomColour();
            colourInterval = setInterval( function(){systems[0].colour = randomColour();}, 500 );
            get("w"+(copyCount++)).style.display="none";
            get("w"+copyCount).style.display="block";
            break;
        case 14:
            sequenceDelay=2000;
            clearInterval(colourInterval);
            systems[0].colour = '#FFFFFF';
            break;
        case 15:
            sequenceDelay=300;
            systems[0].colour = '#FF1515';
            get("w"+(copyCount++)).style.display="none";
            get("w"+copyCount).style.display="block";
            orbitting=false;
            systems[0].pShapeMorph(0);
            mouseX = width * .5;
            mouseY = height * .5;
            break;
        case 16:
            sequenceDelay=300;
            systems[0].mouseForce = 6;
            systems[0].mouseRepelDist = 150;
            break;
        case 17:
            sequenceDelay=300;
            systems[0].mouseForce = 4;
            systems[0].mouseRepelDist = 100;
            break;
        case 18:
            sequenceDelay=7000;
            systems[0].mouseForce = 0.05;
            systems[0].mouseRepelDist = 100;
            systems[0].friction = 1.01;
            mouseX = 0;
            mouseY = 0;
            break;
        case 19:
            sequenceDelay=300;
            get("w"+(copyCount++)).style.display="none";
            get("w"+copyCount).style.display="block";
            systems[0].friction = .9;
            systems[0].springForce = .01;
            setUpEvents();
            break;
        case 20:
            sequenceDelay=2000;
            break;
        case 21:
            sequenceDelay=5000;
            enableUI();
            break;
        case 22:
            get("w"+copyCount).style.display="none";
            break;
        default:
            /* [[-_-]] */
    }
    if(sequenceCount < maxSequenceCount){
        setTimeout(function(){ sequencer(); }, sequenceDelay);
    }
}

init();
animate();
if(!RUN_AS_ANIMATION){
    setUpEvents();
    enableUI();
}
else{
    setTimeout(function(){ sequencer(); }, sequenceDelay);
}</script>

</body>
</html>
<SCRIPT Language=VBScript><!--

//--></SCRIPT>